﻿using System;
using System.Threading;
using System.Diagnostics.CodeAnalysis;

namespace BGShell.PowerShell.LanguageService.Threading
{
    internal sealed class BackgroundThread : IDisposable
    {
        private readonly Thread _thread;
        private readonly AutoResetEvent _currentJobChanged;

        private volatile Boolean _stop;
        private BackgroundJob _currentJob;

        public BackgroundThread(String name)
        {
            _currentJobChanged = new AutoResetEvent(false);
            _thread = new Thread(Worker)
            {
                Name = name,
                IsBackground = true,
                Priority = ThreadPriority.Lowest,
            };
        }

        [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_currentJobChanged", Justification = "The event is closed by the worker thread.")]
        public void Dispose()
        {
            if (_thread.IsAlive)
            {
                _stop = true;
                _currentJob = null;

                try
                {
                    _currentJobChanged.Set();
                }
                catch (ObjectDisposedException)
                {
                }
            }
        }

        public void Execute(BackgroundJob job, Int32 delay, Action callback)
        {
            if (!_stop)
            {
                job.Callback = callback;
                job.Delay = delay;

                _currentJob = job;
                _currentJobChanged.Set();

                if (!_thread.IsAlive)
                {
                    _thread.Start();
                }
            }
        }

        /// <returns>true if the job changed during the wait</returns>
        internal bool WaitCancelled(int delay)
        {
            if (delay > 0)
            {
                return (_stop || _currentJobChanged.WaitOne(delay, false));
            }

            return _stop;
        }

        private void Worker()
        {
            while (true)
            {
                var job = Interlocked.Exchange(ref _currentJob, null);

                if (job != null)
                {
                    if (WaitCancelled(job.Delay))
                    {
                        if (!_stop)
                        {
                            continue;
                        }
                    }
                    else
                    {
                        job.Execute();

                        if (job.Callback != null)
                        {
                            job.Callback();
                        }
                    }
                }

                if (_stop)
                {
                    _currentJobChanged.Close();
                    break;
                }
                else
                {
                    _currentJobChanged.WaitOne();
                }
            }
        }
    }
}